-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restore time_sync module on macOS #75
Conversation
MacOS does not have the `adjtimex` system call. As a result we have stripped out the entire `time_sync` module on non-Linux platforms. However, packages like katsdpcontroller need the `ClockState` enum without the rest. Instead of spreading the complications, restore the `time_sync` module with a fake `adjtimex` instead. It returns the correct time but with artificially large errors in the absence of real values (and the status is always "OK").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not wild about this approach, because it means the sensors will assert things that are just made up, rather than reporting that the values can't be measured. The only meaningful thing it returns is the current time, and that's not even reported as a sensor value. My preference would be
- the dummy
adjtimex()
function raisesNotImplementedError
TimeSyncUpdater
catchesNotImplementedError
and responds by setting all the sensors toinactive
.
Only a couple of tests would need to be disabled on MacOS. If you also added a test that mocked in the NotImplemented implementation and checked the response, you could get test coverage of that fallback on every OS.
src/aiokatcp/adjtimex.py
Outdated
adjtimex = _libc.adjtimex # noqa: F811 | ||
adjtimex.argtypes = [ctypes.POINTER(Timex)] # type: ignore[attr-defined] | ||
adjtimex.restype = ctypes.c_int # type: ignore[attr-defined] | ||
adjtimex.errcheck = _errcheck # type: ignore[attr-defined] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like this approach of replacing a function, and clearly mypy doesn't either. I'd rather have _adjtimex_dummy
and _adjtimex_real
functions (or pick better names) and then either assign one of them to a adjtimex
variable, or have an adjtimex
function that dispatches to the right one (I'd favour the former just for efficiency).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I like the Bruce-mypy tag-team clothesline 🤼♂️ I wanted to generate an AI picture of the event but Dall-E failed me 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be fair: mypy didn't like the assignment to attributes, which apparently did not bother it before (there was only one type: ignore
on the last line, but now they all complained). It was actually flake8 with the issue [Redefinition of unused name from line n].
Instead of kludging adjtimex to produce fake data on non-Linux systems, rather raise `NotImplementedError` and set the relevant sensors to an inactive state in `TimeSyncUpdater`. Test both cases in the unit tests.
Thanks! I've pushed a new version incorporating your suggestions. I can't figure out the failing tests though. It fails on py311 on both Linux and macOS, and it seems to be unrelated to my fixes. In fact, if I rerun the tests on an older commit that previously passed, it now fails there too 🤔 It also doesn't fail on my laptop. Rerunning these tests several times doesn't help, so it's not just a random fluke. It's complaining about a closed event loop in |
The Python 3.11 failures I think are caused by python/cpython#107650, which got backported to 3.11. We'll need to investigate to see if this is a regression in Python or something that we should be handling better. |
I don't think that should hold up this PR, but we should investigate it before making a new release. |
I've opened NGC-1089 for the Python 3.11 issue. |
tests/test_time_sync.py
Outdated
("expected_status",), | ||
[ | ||
pytest.param(Sensor.Status.NOMINAL, marks=_linux), | ||
pytest.param(Sensor.Status.INACTIVE, marks=_non_linux), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than (or perhaps in addition to) doing this test only on non-Linux, I'd suggest doing it on all OSes by using mocking to replace adjtimex
by _no_adjtimex
, which allows the NotImplementedError handling to be tested on any OS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not getting away here :-) Done!
Also don't forget to bump the year in the copyright notice. |
f9f35d6
to
0fa9e13
Compare
This appeases mypy and removes some superfluous bits. Thanks @bmerry for the suggestions. Co-authored-by: Bruce Merry <[email protected]>
0fa9e13
to
883d30a
Compare
Mock adjtimex to _no_adjtimex on all systems.
daafb59
to
5f608a3
Compare
OK, after many force-pushes I'm ready for another round 😅 |
.gitignore
Outdated
@@ -7,3 +7,6 @@ | |||
src/aiokatcp.egg-info | |||
*.pyc | |||
__pycache__ | |||
|
|||
# macOS detritus | |||
.DS_Store |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like that should go into your global core.excludesfile
rather than here, because it's specific to your development environment rather than specific to the repository.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I did not know about that file... I merely cargo-culted some other repo's ignores (katpoint, etc). Much better idea!
tests/test_time_sync.py
Outdated
def test_smoke( | ||
sensors: SensorSet, updater: TimeSyncUpdater, expected_status: Sensor.Status | ||
) -> None: | ||
"""Test with real adjtimex on Linux to ensure it interacts cleanly with kernel.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that docstring is accurate. It only covers one of the two parametrisations.
Come to think of it, I'm not sure parametrisation is really necessary, since they're a partition. You could just write
expected_status = Sensor.Status.NOMINAL if sys.platform == "linux" else Sensor.Status.INACTIVE
inside the test function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I missed the partition. I originally had tests for linux and darwin until I merged them.
tests/test_time_sync.py
Outdated
assert isinstance(sensors["ntp.state"].value, ClockState) | ||
for sensor in sensors.values(): | ||
# Check that it actually got updated | ||
assert sensor.status == Sensor.Status.INACTIVE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of code duplication here. I'd suggest either:
- Parametrise
test_smoke
with a boolean that says whether to mock, and conditionally usemocker
inside the test; or - Factor out a helper function that does all the work of the test, and have the test functions call it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've gone for option 1 and lost 20 lines.
Ignores specific to the user environment is best put in the global `core.excludesfile`.
Optionally pretend that adjtimex is not there, and set the expected sensor status based on that and whether we are on Linux. No need for skipping tests anymore, since `test_smoke` will always run in some shape or form. This consolidates the smoke tests as well. Improve the documentation.
Thanks for the suggestions! You can check again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Go ahead and merge - there is a separate ticket for the Python 3.11 failures.
MacOS does not have the
adjtimex
system call. As a result we have stripped out the entiretime_sync
module on non-Linux platforms.However, packages like katsdpcontroller need the
ClockState
enum without the rest. Instead of spreading the complications, restore thetime_sync
module with a fakeadjtimex
instead.It returns the correct time but with artificially large errors in the absence of real values (and the status is always "OK").
[This was a leftover from the beginning of the year when I wanted to test katsdpcontroller on my laptop. Nearly forgot about it - it was lurking in my working copy for months 🙂 ]